#include <stdio.h>
#include <pthread.h>
#include "zyf_test_NoClass.h"

#define TEST 1
#define ANDROID_MOD 2
#define JNI_VERSION JNI_VERSION_1_6
#define CLASS_NAME "zyf/test/NoClass"
static pthread_key_t _threadKey;
static pthread_t     _tid;
static JavaVM*       _javaVM = NULL;
static jclass        _cls = NULL;

/*********************| 工具函数 |*********************/
static void bello(JNIEnv* env, jclass cls) {
    fprintf(stderr, "将要尝试获取Java中的bello函数地址\n");
    jmethodID func = (*env)->GetStaticMethodID(env, cls, "bello", "()V");
    if (func) {
        (*env)->CallStaticVoidMethod(env, cls, func);
    }
}

static JNIEnv* getEnv() {
    JNIEnv* env = (JNIEnv*)pthread_getspecific(_threadKey);
    if (!env) {
        // 缓冲env
        jint ret = (*_javaVM)->GetEnv(_javaVM, (void**)&env, JNI_VERSION);

        switch (ret) {
        case JNI_OK:
            break;
        case JNI_EDETACHED:
            if ((*_javaVM)->AttachCurrentThread(_javaVM, (void**)&env, NULL) != JNI_OK) {
                fprintf(stderr, "缓冲 env 时attach thread 失败\n");
            }
            break;
        default:
            fprintf(stderr, "获取 env 失败了\n");
            break;
        }
        pthread_setspecific(_threadKey, env);
    }
    return env;
}

static void onJniThreadDestroyed(void* value) {
    // 线程被销毁的时候，将它和Java VM断开链接
    if (value) {
        // 这里可以演示一个重复Detach的错误
        if ((*_javaVM)->GetEnv(_javaVM, &value, JNI_VERSION) != JNI_EDETACHED) {
            (*_javaVM)->DetachCurrentThread(_javaVM);
        }
    }
    pthread_setspecific(_threadKey, NULL);
}

/*********************| 另一个线程 |*********************/
static void* threadFunc(void* arg) {
#if TEST == 0
    JNIEnv* env = (JNIEnv*)arg;
    jclass cls = (*env)->FindClass(env, CLASS_NAME);
    fprintf(stderr, "In %s thread id = %lp \tenv = %p\tcls = %p\n", __func__, pthread_self(), env, cls);

    if (cls) {
        bello(env, cls);
        (*env)->DeleteLocalRef(env, cls);
    }
#else
    JNIEnv* env = getEnv();
    fprintf(stderr, "尝试 FindClass \n");
    if (env) {
#if ANDROID_MOD == 0
        jclass cls = (*env)->FindClass(env, CLASS_NAME);
#else
        jclass cls = _cls;
#endif
        fprintf(stderr, "In %s thread id = %ld \tenv = %p\tcls = %p\n", __func__, pthread_self(), env, cls);
        bello(env, cls);
#if ANDROID_MOD == 0
        (*env)->DeleteLocalRef(env, cls);
#endif
    }
#endif
    return NULL;
}

/*********************| JNI 生命周期 |*********************/
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    _javaVM = vm;
    fprintf(stderr, "In %s thread id = %ld\n", __func__, pthread_self());
#if TEST == 1
    if (pthread_key_create(&_threadKey, onJniThreadDestroyed) != 0) {
        fprintf(stderr, "In %s 创建 thread key 失败\n", __func__);
        return -1;
    }

    // 初始化 _cls
    JNIEnv* env = getEnv();
#if ANDROID_MOD == 1
    _cls = (*env)->FindClass(env, CLASS_NAME);
#elif ANDROID_MOD == 2
    jclass cls = (*env)->FindClass(env, CLASS_NAME);
    _cls = (*env)->NewGlobalRef(env, cls);
    (*env)->DeleteLocalRef(env, cls);
#endif

    threadFunc(NULL);

    fprintf(stderr, "\n现在启动另外一个线程\n");
    if (pthread_create(&_tid, NULL, threadFunc, NULL) != 0) {
        fprintf(stderr, "创建线程失败\n");
        return -1;
    }
#endif
    return JNI_VERSION;
}

void JNI_OnUnload(JavaVM *vm, void *reserved) {
    fprintf(stderr, "In %s thread id = %ld\n", __func__, pthread_self());
#if TEST == 1
    JNIEnv* env = getEnv();
    if (env && _cls) {
#if ANDROID_MOD == 1
        (*env)->DeleteLocalRef(env, _cls);
#elif ANDROID_MOD == 2
        (*env)->DeleteGlobalRef(env, _cls);
#endif
        _cls = NULL;
    }

    _javaVM = NULL;
    pthread_key_delete(_threadKey);
#endif
}

/*********************| startCpp |*********************/
void JNICALL Java_zyf_test_NoClass_startCpp(JNIEnv* env, jclass cls) {
    fprintf(stderr, "In %s thread id = %ld \tcls = %p\n", __func__, pthread_self(), cls);
#if TEST == 0
    threadFunc(env);

    fprintf(stderr, "\n现在启动另外一个线程\n");
    pthread_t tid;
    if (pthread_create(&tid, NULL, threadFunc, env) != 0) {
        fprintf(stderr, "创建线程失败\n");
        return;
    }
    pthread_join(tid, NULL);
#else
    pthread_join(_tid, NULL);
#endif
}
